#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// NETWORK
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>


// CONFIGURATION VARIABLES
#define MAX_HOST_NAME 256
#define MAX_NUM_NODES 20
#define DUMMY_SPACE 3

// ERROR MESSAGE MACROS
#define ERROR_READTOPOLOGY "error_readtopology: populate of forwarding table failed"

// IP/PORT TUPLE
typedef struct Node_ID
{
    struct in_addr IP;
    uint16_t port;
    char isAcked;
} __attribute__((packed)) Node_ID;

// LSP_PACKET STRUCTURE
typedef struct LSP_Packet
{
    Node_ID source;
    uint32_t sequence;
    uint32_t TTL;
    uint32_t num_neighbors;
    Node_ID neighbors[];
} __attribute__((packed)) LSP_Packet;

// TOPOLOGY ELEMENT STRUCTURE
typedef struct Topo_Element
{
    uint32_t length;
    // To implement the linked list
    struct Topo_Element *next_element;

    LSP_Packet packet;
} Topo_Element;

// FORWARD TABLE STRUCTURE
typedef struct Topology_Table
{
    // Keep track of local elements to make life easier
    Node_ID local;
    LSP_Packet *local_packet;

    // Other table features
    Topo_Element *head;
    Topo_Element *tail;
    int num_elements;

} Topology_Table;

void print_Node_ID(Node_ID *node)
{
    if (node == NULL){
        printf("NULL,-1");
    }
    else{
        printf("%s,%d", inet_ntoa(node->IP), node->port);
    }
}

void dump_topo_table(Topology_Table *topo_table)
{
  printf("\033[34mEMULATOR LOADED THE FOLLOWING TOPOLOGY:\033[1;34m\n> num_nodes:\t\t%d\n> local_IP:\t\t%s\n> local_port:\t\t%d\n> num_neighbors:\t%d\n> local_neighbors:\t", topo_table->num_elements, inet_ntoa(topo_table->local.IP), topo_table->local.port, topo_table->local_packet->num_neighbors);

    int i;
    for(i = 0; i < topo_table->local_packet->num_neighbors; i++)
    {
        print_Node_ID(&(topo_table->local_packet->neighbors[i]));
        printf(" ");
    }

    printf("\n\n> head:\t\t");
    print_Node_ID(&(topo_table->head->packet.source));

    printf("\n> tail:\t\t");
    print_Node_ID(&(topo_table->tail->packet.source));
    printf("\n");


    // Uncomment for full printing of topology

    int j;
    Topo_Element *topo_ptr = topo_table->head; 

    for(i = 0; i < topo_table->num_elements; i++)
    {
        printf("\nS%d (seq: %d): ", i+1, topo_ptr->packet.sequence);
	print_Node_ID(&(topo_ptr->packet.source));
        printf("\n  in_N (%d): ", topo_ptr->packet.num_neighbors);

        for(j = 0; j < topo_ptr->packet.num_neighbors; j++)
        {
            print_Node_ID(&(topo_ptr->packet.neighbors[j]));
            printf(" ");
	}
	topo_ptr = topo_ptr->next_element;
    }
    

    printf("\033[0m\n");
}

Topology_Table* init_topo_table(Topology_Table *topo_table, int local_port)
{
    topo_table = malloc(sizeof(Topology_Table));

    // Setup local IP and port
    char hostname[256];
    gethostname(hostname, sizeof(hostname));
    struct hostent *tmp_host = gethostbyname(hostname);
    topo_table->local.IP = *((struct in_addr*)tmp_host->h_addr);
    topo_table->local.port = local_port;

    // Fill in rest of topo_table
    topo_table->head = NULL;
    topo_table->tail = NULL;
    topo_table->local_packet = NULL;
    topo_table->num_elements = 0;

    return topo_table;
}

// Need to do a deep delete (TODO: FIX!)
void delete_topo_table(Topology_Table *table)
{
    Topo_Element *ptr = table->head;
    Topo_Element *tmp;

    while(ptr != NULL)
    {
        tmp = ptr->next_element;
	free(ptr);
	ptr = tmp;
    }

    free(table);
}

// Thanks to the awful work that was done in populate_table(),
// searching for a match is now relatively easy...
// search for a forwarding match
// return NULL if error, ptr to first element in arrayof Node_ID's
// if success
Node_ID* get_topo_neighbor(Topology_Table *topo_table, struct in_addr *src_IP, int src_port)
{
    int i;

    for (i = 0; i < topo_table->local_packet->num_neighbors; i++){

	// These are the droids you are looking for...
        if (memcmp(&(topo_table->local_packet->neighbors[i].IP),
                   src_IP,
                   sizeof(struct in_addr)) == 0 &&
                   topo_table->local_packet->neighbors[i].port == 
                   src_port) {
            return &(topo_table->local_packet->neighbors[i]);
	}
    }

    // If nothing was found
    return NULL;
}

LSP_Packet* get_lsp(Topology_Table *topo_table, Node_ID *node)
{
    Topo_Element *topo_ptr = topo_table->head;

    while (topo_ptr != NULL){

	// These are the droids you are looking for...
        if (memcmp(&(topo_ptr->packet.source.IP),
                   &(node->IP),
                   sizeof(struct in_addr)) == 0 &&
                   topo_ptr->packet.source.port == 
                   node->port) {
            return &(topo_ptr->packet);
	}

    topo_ptr = topo_ptr->next_element;
    }

    // If nothing was found
    return NULL;
}

LSP_Packet* update_topo(Topology_Table *topo_table, LSP_Packet *new_packet)
{
    Topo_Element *topo_add_ptr = NULL;  // For the new neighbor

    Topo_Element *topo_ptr = topo_table->head;
    Topo_Element *topo_prev_ptr = NULL;


    struct in_addr *src_IP = &(new_packet->source.IP);
    int src_port = new_packet->source.port;

    while (topo_ptr != NULL){

	// These are the droids you are looking for...
        if (memcmp(&(topo_ptr->packet.source.IP),
                   src_IP,
                   sizeof(struct in_addr)) == 0 &&
                   topo_ptr->packet.source.port == src_port) {

             // If sequence number we have his higher or equal
             if (topo_ptr->packet.sequence >= new_packet->sequence) {

	       printf("\ntopo seq: %d\nnewp seq: %d\n", topo_ptr->packet.sequence, new_packet->sequence);

                 return NULL;
	     }

             topo_table->num_elements--;

             // Check to see if you are deleting the first element
             if (topo_prev_ptr == NULL) {
                 topo_table->head = topo_ptr->next_element;


                 free(topo_ptr);
                 topo_ptr = topo_ptr->next_element;
	     }

             // Remove element from topology
	     else {
                 if (topo_table->tail == topo_ptr) {

                     topo_table->tail = topo_prev_ptr;
                 }

                 topo_prev_ptr->next_element = topo_ptr->next_element;

                 free(topo_ptr);
                 topo_ptr = topo_prev_ptr->next_element;
	     }

	}

	else {

            // Increment pointers
            topo_prev_ptr = topo_ptr;
	    topo_ptr = topo_ptr->next_element;
	}
    }


    // Make new Topo Element
    int add_len = sizeof(Topo_Element) +
                  sizeof(Node_ID) *
                 (new_packet->num_neighbors + DUMMY_SPACE);
    topo_add_ptr = malloc(add_len);

    // Update the new topo_element
    topo_add_ptr->length = add_len;
    topo_add_ptr->next_element = NULL;

    memcpy(&topo_add_ptr->packet, new_packet, add_len);

    topo_table->tail->next_element = topo_add_ptr;
    topo_table->tail = topo_add_ptr;
    topo_table->num_elements++;

    // Uncomment for debug
    dump_topo_table(topo_table);

    return &(topo_add_ptr->packet);
}
LSP_Packet* remove_topo_neighbor(Topology_Table *topo_table, struct in_addr *src_IP, int src_port)
{
    Topo_Element *topo_ptr = topo_table->head;
    Topo_Element *topo_prev_ptr = NULL;
    Topo_Element *topo_new_ptr = NULL;

    while (topo_ptr != NULL){

	// These are the droids you are looking for...
        if (memcmp(&(topo_ptr->packet.source.IP),
                   src_IP,
                   sizeof(struct in_addr)) == 0 &&
                   topo_ptr->packet.source.port == src_port) {

             topo_table->num_elements--;

             // Check to see if you are deleting the first element
             if (topo_prev_ptr == NULL) {
                 topo_table->head = topo_ptr->next_element;


                 free(topo_ptr);
                 topo_ptr = topo_ptr->next_element;
	     }

             // Remove element from topology
	     else {
                 if (topo_table->tail == topo_ptr) {

                     topo_table->tail = topo_prev_ptr;
                 }

                 topo_prev_ptr->next_element = topo_ptr->next_element;

                 free(topo_ptr);
                 topo_ptr = topo_prev_ptr->next_element;
	     }



	}

	// Make new local LSP Packet
	else if (&(topo_ptr->packet) == topo_table->local_packet) {

            int orig_num =  topo_ptr->packet.num_neighbors;

            // Make new Topo Element
            int element_len = sizeof(Topo_Element) +
                sizeof(Node_ID) *
                (topo_ptr->packet.num_neighbors-1 + DUMMY_SPACE);
            topo_new_ptr = malloc(element_len);

            // Update the new topo_element
            topo_new_ptr->length = element_len;
            topo_new_ptr->next_element =
                topo_ptr->next_element;
            topo_new_ptr->packet.sequence =
                topo_ptr->packet.sequence;
            topo_new_ptr->packet.TTL = topo_ptr->packet.TTL;;
            topo_new_ptr->packet.num_neighbors =
                topo_ptr->packet.num_neighbors-1;
            topo_new_ptr->packet.source.IP =
                topo_ptr->packet.source.IP;
            topo_new_ptr->packet.source.port =
                topo_ptr->packet.source.port;

            // Build the new neighbor array
            int j,k;
            k = 0;

            for (j = 0; j < orig_num; j++){

                // These are the droids you are looking for...
                if (memcmp(&(topo_ptr->packet.neighbors[j].IP),
                        src_IP,
                        sizeof(struct in_addr)) == 0 &&
                        topo_ptr->packet.neighbors[j].port == 
                        src_port) {
                }

		else{
                    topo_new_ptr->packet.neighbors[k].IP = 
                        topo_ptr->packet.neighbors[j].IP;
                    topo_new_ptr->packet.neighbors[k].port = 
                        topo_ptr->packet.neighbors[j].port;
                    topo_new_ptr->packet.neighbors[k].isAcked = 
                        topo_ptr->packet.neighbors[j].isAcked;
                    k++;
		}
            }

	    // Set up tail
            if (topo_table->tail == topo_ptr) {
                 topo_table->tail = topo_new_ptr;
            }

            // Check to see if you are deleting the first element
            if (topo_prev_ptr == NULL) {

	      free(topo_ptr);

                topo_table->head = topo_new_ptr;
                topo_ptr = topo_new_ptr;
	    }

            // Set up connection
	    else {
                topo_prev_ptr->next_element = topo_new_ptr;

                free(topo_ptr);
                topo_ptr = topo_new_ptr;
	    }

            topo_table->local_packet = &(topo_new_ptr->packet);

            // Increment pointers
            topo_prev_ptr = topo_ptr;
	    topo_ptr = topo_ptr->next_element;

	} // End else

	else {

            // Increment pointers
            topo_prev_ptr = topo_ptr;
	    topo_ptr = topo_ptr->next_element;
	}
    }

    // Uncomment for debug
    dump_topo_table(topo_table);

    // If nothing was found
    return &topo_new_ptr->packet;
}

LSP_Packet* add_topo_neighbor(Topology_Table *topo_table, LSP_Packet *new_packet)
{
    Topo_Element *topo_add_ptr = NULL;  // For the new neighbor

    Topo_Element *topo_ptr = topo_table->head;
    Topo_Element *topo_prev_ptr = NULL;
    Topo_Element *topo_new_ptr = NULL;

    while (topo_ptr != NULL){

	// Make new local LSP Packet
	if (&(topo_ptr->packet) == topo_table->local_packet) {

            int orig_num =  topo_ptr->packet.num_neighbors;

            // Make new Topo Element
            int element_len = sizeof(Topo_Element) +
                sizeof(Node_ID) *
                (topo_ptr->packet.num_neighbors+1 + DUMMY_SPACE);
            topo_new_ptr = malloc(element_len);

            // Update the new topo_element
            topo_new_ptr->length = element_len;
            topo_new_ptr->next_element =
                topo_ptr->next_element;
            topo_new_ptr->packet.sequence =
                topo_ptr->packet.sequence;
            topo_new_ptr->packet.TTL = topo_ptr->packet.TTL;;
            topo_new_ptr->packet.num_neighbors =
                topo_ptr->packet.num_neighbors+1;
            topo_new_ptr->packet.source.IP =
                topo_ptr->packet.source.IP;
            topo_new_ptr->packet.source.port =
                topo_ptr->packet.source.port;

            // Build the new neighbor array
            int j,k;
            k = 0;
            for (j = 0; j < orig_num; j++){
                topo_new_ptr->packet.neighbors[k].IP = 
                    topo_ptr->packet.neighbors[j].IP;
                topo_new_ptr->packet.neighbors[k].port = 
                    topo_ptr->packet.neighbors[j].port;
                topo_new_ptr->packet.neighbors[k].isAcked = 
                    topo_ptr->packet.neighbors[j].isAcked;
                k++;
            }

	    // Fill in the new element
            topo_new_ptr->packet.neighbors[k].IP = 
                new_packet->source.IP;
            topo_new_ptr->packet.neighbors[k].port = 
                new_packet->source.port;
            topo_new_ptr->packet.neighbors[k].isAcked = 'T';



            // Set up tail
            if (topo_table->tail == topo_ptr) {
	      topo_table->tail = topo_new_ptr;
            }

            // Check to see if you are deleting the first element
            if (topo_prev_ptr == NULL) {
	      topo_table->head = topo_new_ptr;

              free(topo_ptr);
	      topo_ptr = topo_new_ptr;
            }

            else {
	      // Set up connection
	      topo_prev_ptr->next_element = topo_new_ptr;

              free(topo_ptr);
	      topo_ptr = topo_new_ptr;
            }

            topo_table->local_packet = &(topo_new_ptr->packet);

            // Set up connection
            //topo_prev_ptr->next_element = topo_new_ptr;
            //topo_table->local_packet = &(topo_new_ptr->packet);
            //free(topo_ptr);
            //topo_ptr = topo_new_ptr;

	} // End else

        // Increment pointers
        topo_prev_ptr = topo_ptr;
        topo_ptr = topo_ptr->next_element;
    } // End repopulate thingy


    // Make new Topo Element
    int add_len = sizeof(Topo_Element) +
                  sizeof(Node_ID) *
                 (new_packet->num_neighbors + DUMMY_SPACE);
    topo_add_ptr = malloc(add_len);

    // Update the new topo_element
    topo_add_ptr->length = add_len;
    topo_add_ptr->next_element = NULL;

    memcpy(&topo_add_ptr->packet, new_packet, add_len);

    topo_table->tail->next_element = topo_add_ptr;
    topo_table->tail = topo_add_ptr;
    topo_table->num_elements++;

    // Uncomment for debug
    dump_topo_table(topo_table);

    return &(topo_table->tail->packet);
}

// return -1 if error, 0 if success
int populate_topo_table(Topology_Table *topo_table, char *topo_filename)
{
    FILE *topology_file;
    int status;
    Topo_Element *topo_ptr;

    // For Parsing
    char *line = NULL;
    size_t line_len = 0;
    char *node_tokens[MAX_NUM_NODES]; // Specified by documentation
    char *node_pair[2];
    int num_tokens = 0;


    // If table already has elements in it, return error
    if (topo_table->head != NULL || topo_table->tail != NULL)
    {
        return -1;
    }

    // Open the file containing the forwarding information
    topology_file = fopen(topo_filename,"r");
    if (topology_file == NULL){
        return -1;
    }

    topo_ptr = topo_table->head;

    while(1)
    {
        num_tokens = 0;
        status = getline(&line, &line_len, topology_file);

	// Check if end of file
        if (status == EOF){
            //free(line); // May need to free somewhere for getline
            topo_ptr->next_element = NULL;
            break;
        }

	node_tokens[0] = strtok(line, " ");

	// Fill the array with the parsed pieces
	int i;
	for (i = 1; i < MAX_NUM_NODES; i++){
            node_tokens[i] = strtok(NULL, " ");
	    if (node_tokens[i] != NULL){
                num_tokens++;
	    }
	}

        // Make the head element
	int element_len = sizeof(Topo_Element) +
                sizeof(Node_ID)*(num_tokens + DUMMY_SPACE);

	// Malloc the new topo_element
	if (topo_table->head == NULL){
            topo_ptr = malloc(element_len);
            topo_table->head = topo_ptr;
	}
	else{
            topo_ptr->next_element = malloc(element_len);
	    topo_ptr = topo_ptr->next_element;
	}

	// Update the topo_table
        topo_table->tail = topo_ptr;
	topo_table->num_elements++;

	// Update the new topo_element
	topo_ptr->length = element_len;
	topo_ptr->packet.sequence = 0;
	topo_ptr->packet.TTL = 0;
	topo_ptr->packet.num_neighbors = num_tokens;

	// Fill the source Node_ID
	node_pair[0] = strtok(node_tokens[0], ",");
	node_pair[1] = strtok(NULL, ",");

        inet_aton(node_pair[0], &(topo_ptr->packet.source.IP));
	topo_ptr->packet.source.port = strtol(node_pair[1], NULL, 10);

        // Fill the neightbor Node_ID's
	int j;
	for (j = 1; j < MAX_NUM_NODES; j++){
            if (node_tokens[j] != NULL) {

                node_pair[0] = strtok(node_tokens[j], ",");
                node_pair[1] = strtok(NULL, ",");

                inet_aton(node_pair[0],
                          &(topo_ptr->packet.neighbors[j-1].IP));
                topo_ptr->packet.neighbors[j-1].port =
                          strtol(node_pair[1], NULL, 10);
	    }
	}

	// Set the ptr to the local neighbors
	if (memcmp(&(topo_table->local.IP),
                   &(topo_ptr->packet.source.IP),
                   sizeof(struct in_addr)) == 0 &&
                   topo_table->local.port == 
                   topo_ptr->packet.source.port) {
            topo_table->local_packet = &(topo_ptr->packet);
	}

	// MAY NEED TO DO A FREE SOMEWHERE...


    } // End while(1)

    free(line);

    return 0;
}

